package cn.cttic.auth.service;

import cn.cttic.base.api.domain.BaseUser;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.cttic.app.api.RemoteAppUserService;
import cn.cttic.auth.properties.UserPasswordProperties;
import cn.cttic.common.core.constant.CacheConstants;
import cn.cttic.common.core.constant.Constants;
import cn.cttic.common.core.enums.AppLoginType;
import cn.cttic.common.core.enums.DeviceType;
import cn.cttic.common.core.enums.UserType;
import cn.cttic.common.core.exception.user.CaptchaExpireException;
import cn.cttic.common.core.exception.user.UserException;
import cn.cttic.common.core.utils.MessageUtils;
import cn.cttic.common.core.utils.ServletUtils;
import cn.cttic.common.core.utils.SpringUtils;
import cn.cttic.common.core.utils.StringUtils;
import cn.cttic.common.core.utils.ip.AddressUtils;
import cn.cttic.common.log.event.LogininforEvent;
import cn.cttic.common.redis.utils.RedisUtils;
import cn.cttic.common.satoken.model.AppLoginUser;
import cn.cttic.common.satoken.utils.AppLoginHelper;
import cn.cttic.common.satoken.utils.StpUserUtil;
import cn.cttic.system.api.RemoteLogService;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.function.Supplier;

/**
 * 登录校验方法
 *
 * @author Times.Studio
 */
@Service
public class AppLoginService {

    @DubboReference
    private RemoteLogService remoteLogService;
    @DubboReference
    private RemoteAppUserService remoteAppUserService;

    @Autowired
    private UserPasswordProperties userPasswordProperties;

    /**
     * 登录
     */
    public String login(String username, String password) {
        AppLoginUser userInfo = remoteAppUserService.getUserInfo(username);

        checkLogin(AppLoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, userInfo.getPassword()));
        // 获取登录token
        AppLoginHelper.loginByDevice(userInfo, DeviceType.APP);

        recordLogininfor("000000", username, Constants.LOGIN_SUCCESS, MessageUtils.message("appuser.login.success"));

        return StpUserUtil.getTokenValue();
    }

    /**
     * 退出登录
     */
    public void logout() {
        try {
            AppLoginUser loginUser = AppLoginHelper.getAppLoginUser();
            recordLogininfor("000000", loginUser.getUserName(), Constants.LOGOUT, MessageUtils.message("appuser.logout.success"));
        } catch (NotLoginException ignored) {
        } finally {
            try {
                StpUserUtil.logout();
            } catch (NotLoginException ignored) {
            }
        }
    }

    /**
     * 记录登录信息
     *
     * @param username 用户名
     * @param status   状态
     * @param message  消息内容
     * @return
     */
    public void recordLogininfor(String tenantId, String username, String status, String message) {
        // 封装对象
        LogininforEvent logininforEvent = new LogininforEvent();
        logininforEvent.setTenantId(tenantId);
        logininforEvent.setUsername(username);
        logininforEvent.setStatus(status);
        logininforEvent.setMessage(message);
        SpringUtils.context().publishEvent(logininforEvent);
    }

    /**
     * 登录校验
     */
    private void checkLogin(AppLoginType loginType, String username, Supplier<Boolean> supplier) {
        String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
        String loginFail = Constants.LOGIN_FAIL;
        Integer maxRetryCount = userPasswordProperties.getMaxRetryCount();
        Integer lockTime = userPasswordProperties.getLockTime();

        // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
        Integer errorNumber = RedisUtils.getCacheObject(errorKey);
        // 锁定时间内登录 则踢出
        if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) {
            recordLogininfor("000000", username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
            throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
        }

        if (supplier.get()) {
            // 是否第一次
            errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
            // 达到规定错误次数 则锁定登录
            if (errorNumber.equals(maxRetryCount)) {
                RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
                recordLogininfor("000000", username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
                throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
            } else {
                // 未达到规定错误次数 则递增
                RedisUtils.setCacheObject(errorKey, errorNumber);
                recordLogininfor("000000", username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
                throw new UserException(loginType.getRetryLimitCount(), errorNumber);
            }
        }
        // 登录成功 清空错误次数
        RedisUtils.deleteObject(errorKey);
    }
}
